package com.google.code.netbuilder.logging;

import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;
import org.slf4j.spi.LocationAwareLogger;

/**
 * A utility that provides standard mechanisms for logging certain kinds of
 * activities.
 *
 * @author Ralph Goers
 * @author Ceki G&uuml;lc&uuml;
 */
public class NetBuilderLogger extends NetBuilderLoggerWrapper implements Logger {

    private static final String FQCN = NetBuilderLogger.class.getName();
    static final Marker FLOW_MARKER = MarkerFactory.getMarker("FLOW");
    static final Marker ENTRY_MARKER = MarkerFactory.getMarker("ENTRY");
    static final Marker EXIT_MARKER = MarkerFactory.getMarker("EXIT");
    static final Marker EXCEPTION_MARKER = MarkerFactory.getMarker("EXCEPTION");
    static final Marker THROWING_MARKER = MarkerFactory.getMarker("THROWING");
    static final Marker CATCHING_MARKER = MarkerFactory.getMarker("CATCHING");
    static final String EXIT_MESSAGE_0 = "]";
    static final String EXIT_MESSAGE_1 = "] return ({})";
    static final String ENTRY_MESSAGE_0 = "[";
    static final String ENTRY_MESSAGE_1 = "[ params ({})";
    static final String ENTRY_MESSAGE_2 = "[ params ({}, {})";
    static final String ENTRY_MESSAGE_3 = "[ params ({}, {}, {})";
    static final String ENTRY_MESSAGE_4 = "[ params ({}, {}, {}, {})";
    static final int ENTRY_MESSAGE_ARRAY_LEN = 5;
    static final String[] ENTRY_MESSAGE_ARRAY = new String[ENTRY_MESSAGE_ARRAY_LEN];

    static {
        ENTRY_MARKER.add(FLOW_MARKER);
        EXIT_MARKER.add(FLOW_MARKER);
        THROWING_MARKER.add(EXCEPTION_MARKER);
        CATCHING_MARKER.add(EXCEPTION_MARKER);

        ENTRY_MESSAGE_ARRAY[0] = ENTRY_MESSAGE_0;
        ENTRY_MESSAGE_ARRAY[1] = ENTRY_MESSAGE_1;
        ENTRY_MESSAGE_ARRAY[2] = ENTRY_MESSAGE_2;
        ENTRY_MESSAGE_ARRAY[3] = ENTRY_MESSAGE_3;
        ENTRY_MESSAGE_ARRAY[4] = ENTRY_MESSAGE_4;
    }

    public enum Level {

        TRACE("TRACE", LocationAwareLogger.TRACE_INT), DEBUG("DEBUG",
        LocationAwareLogger.DEBUG_INT), INFO("INFO",
        LocationAwareLogger.INFO_INT), WARN("WARN",
        LocationAwareLogger.WARN_INT), ERROR("ERROR",
        LocationAwareLogger.ERROR_INT);
        private final String name;
        private final int level;

        @Override
        public String toString() {
            return this.name;
        }

        public int intValue() {
            return this.level;
        }

        private Level(String name, int level) {
            this.name = name;
            this.level = level;
        }
    }

    /**
     * Given an underlying logger, construct an NetBuilderLogger
     *
     * @param logger underlying logger
     */
    public NetBuilderLogger(Logger logger) {
        super(logger, NetBuilderLoggerWrapper.class.getName());
    }

    /**
     * NetBuilderLogger method entry.
     *
     * @param argArray supplied parameters
     */
    public void entry(Object... argArray) {
        if (instanceofLAL && logger.isTraceEnabled(ENTRY_MARKER)) {
            String[] strings = objArrayToStringArray(argArray);
            String messagePattern;
            if (strings.length < ENTRY_MESSAGE_ARRAY_LEN) {
                messagePattern = ENTRY_MESSAGE_ARRAY[strings.length];
            } else {
                messagePattern = buildMessagePattern(strings.length);
            }
            FormattingTuple tp = MessageFormatter.arrayFormat(messagePattern, strings);
            ((LocationAwareLogger) logger).log(ENTRY_MARKER, FQCN,
                    LocationAwareLogger.TRACE_INT, tp.getMessage(), strings, tp.getThrowable());
        }
    }

    /**
     * NetBuilderLogger method exit
     */
    public void exit() {
        if (instanceofLAL && logger.isTraceEnabled(ENTRY_MARKER)) {
            ((LocationAwareLogger) logger).log(EXIT_MARKER, FQCN,
                    LocationAwareLogger.TRACE_INT, EXIT_MESSAGE_0, null, null);
        }
    }

    /**
     * NetBuilderLogger method exit
     *
     * @param result The result of the method being exited
     */
    public <R> R exit(R result) {
        if (instanceofLAL && logger.isTraceEnabled(ENTRY_MARKER)) {
            String strResult = specificToString(result);
            FormattingTuple tp = MessageFormatter.format(EXIT_MESSAGE_1, strResult);
            ((LocationAwareLogger) logger).log(EXIT_MARKER, FQCN,
                    LocationAwareLogger.TRACE_INT, tp.getMessage(),
                    new Object[]{
                strResult
            }, tp.getThrowable());
        }
        return result;
    }

    /**
     * NetBuilderLogger an exception being thrown. The generated log event uses
     * Level ERROR.
     *
     * @param throwable the exception being caught.
     */
    public void throwing(Throwable throwable) {
        if (instanceofLAL) {
            ((LocationAwareLogger) logger).log(THROWING_MARKER, FQCN,
                    LocationAwareLogger.ERROR_INT, "throwing", null, throwable);
        }
    }

    /**
     * NetBuilderLogger an exception being thrown allowing the log level to be
     * specified.
     *
     * @param level the logging level to use.
     * @param throwable the exception being caught.
     */
    public void throwing(Level level, Throwable throwable) {
        if (instanceofLAL) {
            ((LocationAwareLogger) logger).log(THROWING_MARKER, FQCN, level.level,
                    "throwing", null, throwable);
        }
    }

    /**
     * NetBuilderLogger an exception being caught. The generated log event uses
     * Level ERROR.
     *
     * @param throwable the exception being caught.
     */
    public void catching(Throwable throwable) {
        if (instanceofLAL) {
            ((LocationAwareLogger) logger).log(CATCHING_MARKER, FQCN,
                    LocationAwareLogger.ERROR_INT, "catching", null, throwable);
        }
    }

    /**
     * NetBuilderLogger an exception being caught allowing the log level to be
     * specified.
     *
     * @param level the logging level to use.
     * @param throwable the exception being caught.
     */
    public void catching(Level level, Throwable throwable) {
        if (instanceofLAL) {
            ((LocationAwareLogger) logger).log(CATCHING_MARKER, FQCN, level.level,
                    "catching", null, throwable);
        }
    }

    private static String buildMessagePattern(int len) {
        StringBuilder sb = new StringBuilder();
        sb.append(" [ params (");
        for (int i = 0; i < len; i++) {
            sb.append("{}");
            if (i != len - 1) {
                sb.append(", ");
            }
        }
        sb.append(')');
        return sb.toString();
    }
}
